home *** CD-ROM | disk | FTP | other *** search
- /*
- File: HIDReader.c
-
- Contains: HID Library Example
-
- Version: 1.0 for use with USB DDK 1.4
-
-
- Copyright: © 1999 by Apple Computer, Inc., all rights reserved.
-
-
- To get current values and set them, this code requires USB 1.4, which
- is newer than Mac OS 9. The example is written with enough checking
- built in that it can safely execute on earlier USB versions.
- This example addresses a number of issues involved with using the HID
- Library to interact with Human Input USB Devices. I have tried to group
- the code for each issue in a single module separated by comments. For
- this reason, not all sections may be necessary for your use. Even within
- a section there may be more code than you need to use. Error checking
- and memory allocation is rather simplistic to easily survive the affects
- of removing unnecessary code.
- */
-
- #define CALL_NOT_IN_CARBON 1
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <errors.h>
- #include <USB.h>
- #include <HID.h>
-
- // How do we find out what vendor ID and product ID we are looking for?
- // Use USB Prober to check under the Device Descriptor.
- // I have also used USB Prober to examine the Parsed Report Descriptor
- // under the device's Configuration Descriptor. I chose a usage that i
- // knew allowed values to be set. In this case RemainingCapacityLimit,
- // which is usage 41 on HID usage page 133. Find the listing for this
- // item in the descriptor and scan upward to find report ID and report
- // size that correspond to it. Also note that the item is described as
- // Feature(Data, Variable, Absolute, Non-volatile). In the same fashion
- // it may be possible for you to extract the necessary information to
- // interact with the HID item of your choice.
- #define myVendorID 0x051D
- #define myProductID 0x0002
-
- #define targetUsage 41
- #define targetUsagePage 133
- #define targetReportID 17
- #define targetReportSize 24
-
-
- // Forward declarations:
- void InstallReportHandler();
- void RemoveReportHandler();
- void MyHIDReportHandler(void * inHIDReport, UInt32 inHIDReportLength,
- UInt32 inRefcon);
-
- // Data shared between functions
- HIDDeviceDispatchTablePtr mHIDDispatchTable;
- USBDeviceDescriptorPtr mUSBDeviceDesc;
- HIDPreparsedDataRef mParsedHIDRef;
- HIDDeviceConnectionRef mHIDDeviceConnectionRef;
-
- UInt32 mReportID = targetReportID;
- UInt32 mCollection = 0;
-
-
- int main(void)
- {
- // Data shared between sections
- CFragConnectionID usbConnID;
- CFragSymbolClass symClass;
- THz currentZone;
- OSErr err;
-
-
- // ***** Step 1: Find Your Device:
-
- USBDeviceRef usbDeviceRef = kNoDeviceRef;
- Boolean foundMyDevice = false;
-
- while (!foundMyDevice)
- {
- err = USBGetNextDeviceByClass (&usbDeviceRef, &usbConnID, kUSBHIDClass,
- kUSBAnySubClass, kUSBAnyProtocol);
- if (err) return err;
-
- // Need to be in the system zone when we search for the symbol.
- currentZone = GetZone ();
- SetZone (SystemZone ());
- err = FindSymbol (usbConnID, "\pTheUSBDriverDescription",
- (Ptr *)&mUSBDeviceDesc, &symClass);
- SetZone (currentZone);
-
- if (mUSBDeviceDesc->vendor == myVendorID &&
- mUSBDeviceDesc->product == myProductID)
- {
- foundMyDevice = true;
- }
-
- // If the driver that matched our device was the HID class driver,
- // it does not belong to a specific device, so it has vendor and
- // product ids of 0, 0. In that case, we have to check farther.
-
- if (mUSBDeviceDesc->vendor == 0 &&
- mUSBDeviceDesc->product == 0)
- {
- // Now we are going to get information from the device itself.
- currentZone = GetZone();
- SetZone(SystemZone());
- err = FindSymbol(usbConnID, "\pTheHIDDeviceDispatchTable",
- (Ptr *)&mHIDDispatchTable, &symClass);
- SetZone(currentZone);
- if (err) continue; // There may be other devices to check.
-
- UInt16 theVendorID = 0;
- UInt32 size = sizeof(UInt16);
- err = (*mHIDDispatchTable->pHIDGetDeviceInfo)(kHIDGetInfo_VendorID,
- &theVendorID, &size);
- if (err) continue;
-
- UInt16 theProductID = 0;
- size = sizeof(UInt16);
- err = (*mHIDDispatchTable->pHIDGetDeviceInfo)(kHIDGetInfo_ProductID,
- &theProductID, &size);
- if (err) continue;
-
- if (theVendorID == myVendorID && theProductID == myProductID)
- {
- foundMyDevice = true;
- }
- }
- }
-
- // Don't have to check foundMyDevice, since only other way out
- // of loop was return.
-
-
- // ***** Step 2: HID Library Setup:
-
- #define kMaxReportDescSize 1024
-
- UInt8 * mHIDReportDesc;
- UInt32 mHIDReportDescLength;
-
- currentZone = GetZone();
- SetZone(SystemZone());
- err = FindSymbol(usbConnID, "\pTheHIDDeviceDispatchTable",
- (Ptr *)&mHIDDispatchTable, &symClass);
- SetZone(currentZone);
- if (err) return err;
-
- mHIDReportDesc = (UInt8 *)NewPtrClear(kMaxReportDescSize);
- if (mHIDReportDesc == nil) return MemError();
-
- mHIDReportDescLength = kMaxReportDescSize;
- err = (*mHIDDispatchTable->pHIDGetHIDDescriptor)(kUSBReportDesc, 0,
- mHIDReportDesc, &mHIDReportDescLength);
- if (!err)
- err = HIDOpenReportDescriptor(mHIDReportDesc, mHIDReportDescLength,
- &mParsedHIDRef, kHIDFlag_StrictErrorChecking);
- // Some HID report descriptors may have minor errors that trip up our call
- // when kHIDFlag_StrictErrorChecking is on. If may be possible to try this
- // with 0 error checking and have the open succeed.
-
- DisposePtr((char *)mHIDReportDesc);
-
- if (err) goto finalcleanup;
-
-
- // ***** Step 2b: Dynamically Finding Report Info:
-
- // HIDValueCaps & HIDButtonCaps may be necessary for info to use in HID
- // calls. (For what information is actually necessary, see the discussion
- // in the comments from sections on "Get Current Value" and "Change Value".)
- // For this example, i know that i want to work with a feature value type of
- // HID element and that i would like to find it's reportID and "collection".
- // Note: If we have already determined these values from USB Prober, there
- // is no need to go through this dynamic lookup.
-
- HIDCaps mMaxCaps;
- UInt32 numFeatureValues;
- HIDValueCaps * vcPtr = nil;
- // For buttons, use HIDButtonCaps.
-
- // Get statistics on how many of each type of HID item.
- err = HIDGetCaps(mParsedHIDRef, &mMaxCaps);
-
- if (!err)
- { // There can be 6 arrays of HID report info: Both values and buttons can
- // be grouped into kHIDInputReport, kHIDOutputReport, and kHIDFeatureReport.
- // For simplicity, we will only check for a feature value at this time.
- numFeatureValues = mMaxCaps.numberFeatureValueCaps;
- vcPtr =
- (HIDValueCaps *)NewPtrClear(sizeof(HIDValueCaps) * numFeatureValues);
-
- if (vcPtr != nil)
- {
- err = HIDGetValueCaps(kHIDFeatureReport, vcPtr, &numFeatureValues,
- mParsedHIDRef);
- if (!err)
- { // We have our information and can search for specific case.
- for (int i = 0; i < numFeatureValues; i++)
- {
- // Before treating the usage field as a real usage, we
- // should have checked the isRange flag. However, we
- // won't match the targetUsage even if it is usageMin.
- if (vcPtr[i].u.notRange.usage == targetUsage &&
- vcPtr[i].usagePage == targetUsagePage)
- {
- mReportID = vcPtr[i].reportID;
- mCollection = vcPtr[i].collection;
- break;
- }
- }
- }
-
- DisposePtr((char *)vcPtr);
- }
- }
-
- if (err) goto finalcleanup;
-
-
- // ***** Step 3: Setup Report Handler:
-
- // The report handler will normally just let us know when a value has changed.
- // But what if we want to Get an initial value?
- // Normally we would do some sychronous read of the value we are interested in.
- // However, USB is running at interrupt time and HID Library has gone to great
- // lengths to shield us from that. So the way to cleanly get a value is to use
- // the report handler. We will install the report handler and then request a
- // report for the value we are interested in.
-
- InstallReportHandler();
-
-
- // ***** Step 4: Get Current Value:
-
- // Installing the report handler will let us know when values change
- // because that is when reports are issued. To get initial values, however,
- // we may have to request them.
-
- // Prior to USB 1.4, the pHIDGetReport vector was nil, so there was no
- // way to get a current value until after Mac OS 9.0.
- if (mHIDDispatchTable->pHIDGetReport == nil) goto waitforinput;
-
- // We are not only asking for the value we are interested in, but also
- // any value that shares that reportID.
- err = (*mHIDDispatchTable->pHIDGetReport)(mHIDDeviceConnectionRef,
- kHIDFeatureReport, mReportID, MyHIDReportHandler, 0);
-
-
- // ***** Step 5: Change Value:
-
- // To use SetValue, we discovered that we needed more information than
- // was easily available. To recitify this, future releases of the HID
- // Library may include an expanded API that will handle some of the
- // setup we are doing here.
-
- // Prior to USB 1.4, the pHIDSetReport vector was nil, so there was no
- // way to get a current value until after Mac OS 9.0.
- if (mHIDDispatchTable->pHIDSetReport == nil) goto waitforinput;
-
- // Create storage for the HID report we are going to send.
- // The first thing we need to know is what size record to create. There
- // is NO API in the HID Library that allows us to find this out. So for
- // now, we must look at the HID Report Descriptor displayed by USB Prober.
- // Remember that the pertinent size is the one that is listed closest
- // above the item you are interested in's usage. The size displayed there
- // is in bits, but the size we use in HID Library calls is in byte. In this
- // example we have a size of 24. To get bytes we use the formula:
- // bytes = (bits + 7) / 8, which keeps us from rounding off those odd
- // numbers of bits. Not only that, but when there are many possible
- // reportID's, we need an extra byte to hold the reportID we want.
- // So in our example we have found the byte size to be 4. Complicating
- // this is the fact that many HID devices have only a default report type
- // that effectively has a reportID of 0. In those cases, the HID spec lets
- // us transmit just the raw values without the extra ID byte. One way to
- // recognize this is if the call with your original size calculation returns
- // an error value of kHIDInvalidReportLengthErr, you can step the size down
- // by 1. (Clearly we need an API to fully construct these reports that takes
- // all of this into account.)
-
- UInt32 newValue = 30; // Random number for our example.
- UInt32 reportSize = 4;
- UInt8 * reportPtr = (UInt8 *)::NewPtrClear(reportSize);
- if (reportPtr == nil) goto waitforinput;
-
- // In the case of not needing the reportID, mReportID = 0, so we're OK.
- *reportPtr = mReportID;
-
- // The HID Library can now build the necessary HID report. Note here that
- // a collection value of 0 is usually sufficient for the setup calls.
- err = HIDSetUsageValue(kHIDFeatureReport, targetUsagePage, mCollection,
- targetUsage, newValue, mParsedHIDRef, reportPtr, reportSize);
- if (err) goto setvaluecleanup;
-
- // Actually send the report.
- err = (*mHIDDispatchTable->pHIDSetReport)(mHIDDeviceConnectionRef,
- kHIDFeatureReport, mReportID, reportPtr, reportSize);
- if (err) goto setvaluecleanup;
-
- // Optional step. When a value is sent to a HID device, it usually does not
- // reply with a new report showing the changed value. So if you are using
- // your report handler to keep track of current values, you may want to
- // request a new report for this item.
- err = (*mHIDDispatchTable->pHIDGetReport)(mHIDDeviceConnectionRef,
- kHIDFeatureReport, mReportID, MyHIDReportHandler, 0);
-
- setvaluecleanup:
- // HID Library makes it's own copy of our data, so we can free it now.
- if (reportPtr != nil) DisposePtr((char *)reportPtr);
-
-
- // ***** Step 6: Display Output:
-
- // Allow time for reports to come in and be handled before quiting.
- // The report handler will be given time to add output to USB Prober's
- // Expert Log window.
- waitforinput:
- printf("View report handling in USB Prober's Expert Log window.\n");
- printf("Type 'q' to quit handling reports.\n");
- fflush(nil);
- int stopChar = 0;
- while (stopChar != 'q' && stopChar != 'Q') stopChar = getchar();
-
-
- // ***** Step 7: Cleanup:
-
- finalcleanup:
- RemoveReportHandler();
-
- printf("Removed report handler.\n");
- fflush(nil);
-
- if (mParsedHIDRef != nil)
- {
- HIDCloseReportDescriptor(mParsedHIDRef);
- mParsedHIDRef = nil;
- }
-
- return 0;
- }
-
-
- // • ReportHandler
- //
- // The key to how the report handler works is that when it gets called to
- // process a report, it must pass the report through one of the HID Library
- // functions to extract the desired value. These decoder functions are called
- // HIDGetxxx (not HIDGetxxxCaps): HIDGetUsageValue, HIDGetScaledUsageValue,
- // HIDGetUsageValueArray, HIDGetButtons, and HIDGetButtonsOnPage.
- // Reports are compressed data that may contain multiple values within. They
- // also may or may not have reportID as the first byte of data. A brute force
- // way to handle the confusion is to just set up a series of decode calls for
- // each value you are interested in and apply the incomming report to each of
- // them, accepting only those that return with noErr.
-
- void MyHIDReportHandler(void * inHIDReport, UInt32 inHIDReportLength,
- UInt32 inRefcon)
- {
- // In the HID Browser, i have multiple devices open, each with it's own
- // set of values. There i pass the pointer to the device's variables
- // as the refcon.
- #pragma unused(inRefcon)
-
- // A special note for using HIDGetButtonsOnPage: This call is going to
- // return an array of HIDUsage's that corresponds to each button that is
- // on. In the advent of there being no buttons on, rather than just
- // returning a 0 length array, it comes back with kHIDUsageNotFoundErr.
-
- // We're making it easy. We're only looking for one specific value.
- SInt32 reportValue;
- OSErr err;
-
- err = HIDGetUsageValue(kHIDFeatureReport, targetUsagePage, mCollection,
- targetUsage, &reportValue, mParsedHIDRef, inHIDReport,
- inHIDReportLength);
- if (err) return;
-
- // My first attempt at a cheap thing to do with our output, was to use printf.
- // After crashing due to interference between printf's in the main code thread
- // and this handler, i hit upon the much better expediant of sending our output
- // to USB Prober's Expert Log window. Plus, this is a great example of an
- // invaluable debugging tool for USB.
- USBExpertStatusLevel(kUSBStatusLevelGeneral, 0,
- "\pMyHIDReportHandler Value: ", (UInt32)reportValue);
- }
-
-
- // • InstallReportHandler
- //
- // Since InstallReportHandler can be called from various locations,
- // it does all it's own setup validation before doing the actual
- // install. If it fails, it leaves the state such that RemoveReportHandler
- // can also be called and perform only necessary cleanup.
-
- void InstallReportHandler()
- {
- HIDDeviceConnectionRef tempDeviceConnectionRef;
- OSErr err;
-
- // Checking to see if device open already.
- if (mHIDDeviceConnectionRef != 0) return;
-
- // Are we setup to open?
- if (mHIDDispatchTable == nil) return;
-
- // Open the device.
- err = (*mHIDDispatchTable->pHIDOpenDevice)
- (&tempDeviceConnectionRef, kHIDPerm_ReadWriteShared, 0);
-
- if (err) return;
-
- // The device is open, so let's install our handler.
- err = (*mHIDDispatchTable->pHIDInstallReportHandler)
- (tempDeviceConnectionRef, 0, MyHIDReportHandler, 0);
-
- // Don't leave device open if we got an error.
- if (err)
- {
- (*mHIDDispatchTable->pHIDCloseDevice)(tempDeviceConnectionRef);
- return;
- }
-
- // Signal successful opening.
- mHIDDeviceConnectionRef = tempDeviceConnectionRef;
- }
-
-
- // • RemoveReportHandler
- //
- // Since RemoveReportHandler can be called from various locations,
- // it does all it's own setup validation before doing the actual remove.
-
- void RemoveReportHandler()
- {
- OSErr err;
-
- // If no indication that we successfully installed, don't remove.
- if (mHIDDeviceConnectionRef == 0) return;
-
- // Remove the handler.
- err = (*mHIDDispatchTable->pHIDRemoveReportHandler)(mHIDDeviceConnectionRef);
-
- // Removing the report handler also restores the previous handler, if any.
- // In event of an error so that is not done, it may still be better to fall
- // through and try to close the device anyway?
- if (err) return;
-
- // Release the device.
- err = (*mHIDDispatchTable->pHIDCloseDevice)(mHIDDeviceConnectionRef);
-
- if (err) return;
-
- // Signal successful closing.
- mHIDDeviceConnectionRef = 0;
- }
-
-